package de.jmda.core.util.jpa;

import static de.jmda.core.mproc.ProcessingUtilities.getElementsAnnotatedWith;
import static de.jmda.core.mproc.ProcessingUtilities.isProcessingOver;
import static de.jmda.core.mproc.ProcessingUtilities.normalizeBinaryClassName;
import static java.util.Arrays.asList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.lang.annotation.Annotation;
import java.util.List;
import java.util.Set;

import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.ElementFilter;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;

import org.junit.Test;

import de.jmda.core.mproc.Processor;
import de.jmda.core.util.jpa.EntityRelation;
import de.jmda.core.util.jpa.EntityRelationInfo;
import de.jmda.core.util.jpa.EntityRelationInfoBidirectional;
import de.jmda.core.util.jpa.EntityRelationInfoFactory;
import de.jmda.core.util.jpa.EntityRelationInfoManyToOneBidirectional;
import de.jmda.core.util.jpa.EntityRelationInfoOneToManyBidirectional;

public class JUTEntityRelationInfoFactoryOneToManyBi
extends AbstractJUTEntityRelationInfoFactory
{
	private VariableElement fieldSource;
	private VariableElement fieldTarget;

	/**
	 * This class will be examined by {@link PrivateProcessor}
	 */
	private class PrivateClassSource
	{
		@OneToMany(mappedBy="source")
		private Set<PrivateClassTarget> targets;
	}

	/**
	 * This class will be examined by {@link PrivateProcessor}
	 */
	private class PrivateClassTarget
	{
		@ManyToOne
		private PrivateClassSource source;
	}

	@SupportedAnnotationTypes(
			value = { "javax.persistence.OneToMany", "javax.persistence.ManyToOne" })
	@SupportedSourceVersion(value = SourceVersion.RELEASE_7)
	private class PrivateProcessor extends Processor
	{
		@Override
		protected boolean process()
		{
			if (isProcessingOver() == false)
			{
				Set<VariableElement> fields =
						ElementFilter.fieldsIn(getElementsAnnotatedWith(OneToMany.class));

				if (fields.size() == 1)
				{
					fieldSource = fields.iterator().next();
				}
				else
				{
					fail("unexpected size of fields");
				}

				fields =
						ElementFilter.fieldsIn(getElementsAnnotatedWith(ManyToOne.class));

				if (fields.size() == 1)
				{
					fieldTarget = fields.iterator().next();
				}
				else
				{
					fail("unexpected size of fields");
				}
			}

			if (fieldSource == null)
			{
				fail("fieldSource is null");
			}
			if (fieldTarget == null)
			{
				fail("fieldTarget is null");
			}

			return false;
		}

		@Override
		protected Set<Class<? extends Annotation>> getSupposedSupportedAnnotationTypes()
		{
			return asSet(OneToMany.class, ManyToOne.class);
		}
	}

	@Override
	protected List<? extends Processor> getProcessors()
	{
		return asList(new PrivateProcessor());
	}

	@Test
	public void testFieldSource()
	{
		EntityRelationInfo entityRelationInfo =
				EntityRelationInfoFactory.getEntityRelationInfo(fieldSource);

		assertNotNull(entityRelationInfo);

		assertTrue(entityRelationInfo instanceof EntityRelationInfoBidirectional);
		assertTrue(entityRelationInfo instanceof EntityRelationInfoOneToManyBidirectional);

		assertEquals(
				entityRelationInfo.getDeclaringType().toString(),
				normalizeBinaryClassName(PrivateClassSource.class));
		assertEquals(
				"targets",
				entityRelationInfo.getField().getSimpleName().toString());
		assertEquals(
				fieldTarget,
				entityRelationInfo.getOwningField());
		assertEquals(
				fieldSource,
				((EntityRelationInfoBidirectional) entityRelationInfo).getInverseField());

		EntityRelation entityRelation = entityRelationInfo.getEntityRelation();

		assertTrue(EntityRelation.isOneToMany(entityRelation));
		assertTrue(EntityRelation.isBidirectional(entityRelation));
	}

	@Test
	public void testFieldTarget()
	{
		EntityRelationInfo entityRelationInfo =
				EntityRelationInfoFactory.getEntityRelationInfo(fieldTarget);

		assertNotNull(entityRelationInfo);

		assertTrue(entityRelationInfo instanceof EntityRelationInfoBidirectional);
		assertTrue(entityRelationInfo instanceof EntityRelationInfoManyToOneBidirectional);

		assertEquals(
				entityRelationInfo.getDeclaringType().toString(),
				normalizeBinaryClassName(PrivateClassTarget.class));
		assertEquals(
				"source",
				entityRelationInfo.getField().getSimpleName().toString());
		assertEquals(
				fieldTarget,
				entityRelationInfo.getOwningField());
		assertEquals(
				fieldSource,
				((EntityRelationInfoBidirectional) entityRelationInfo).getInverseField());

		EntityRelation entityRelation = entityRelationInfo.getEntityRelation();

		assertTrue(EntityRelation.isManyToOne(entityRelation));
		assertTrue(EntityRelation.isBidirectional(entityRelation));
	}
}